package executor;

import java.awt.*;

/**
 * Classe che rappresenta un nodo nel quad-tree.
 * Memorizza la larghezza in pixel del nodo,
 * il colore del nodo (colore originale dell'immagine se il nodo
 * e' foglia, altrimenti media del colore dei pixel ricoperti),
 * e il valore di errore sul colore (uguale a (0,0,0) se foglia,
 * altrimenti uguale alla massima differenza tra il colore del nodo e il
 * colore di uno dei pixel ricoperti, calcolato separatemente per le
 * tre componenti rossa, verde, blu).
 * Memorizza inoltre i 4 figli (se il nodo non e' foglia), una marcatura
 * booleana che indica se il nodo e' stato preso nel corrente troncamento
 * del quad-tree.
 * Come variabili di classe tiene i tre colori usati per generare l'immagine
 * della suddivisione: per i bordi, per l'interno dei nodi che non sono singoli
 * pixel, per i nodi che sono singoli pixel.
 * Ha inoltre un contatore dei nodi visitati, usato a fini statistici.
 */
public class QuadNode
{
  /** Rappresenta il lato nord di un nodo. */
  public static final int NORTH = 0x4;
  /** Rappresenta il lato sud di un nodo. */
  public static final int SOUTH = 0x8;
  /** Rappresenta il lato est di un nodo. */
  public static final int EAST = 0x2;
  /** Rappresenta il lato ovest di un nodo. */
  public static final int WEST = 0x1;
  
  /** Rappresenta l'angolo nord-est di un nodo. */
  public static final int NE = NORTH | EAST;
  /** Rappresenta l'angolo nord-ovest di un nodo. */
  public static final int NW = NORTH | WEST;
  /** Rappresenta l'angolo sud-est di un nodo. */
  public static final int SE = SOUTH | EAST;
  /** Rappresenta l'angolo sud-ovest di un nodo. */
  public static final int SW = SOUTH | WEST;

  /** Larghezza del nodo, deve essere una potenza di 2. */
  protected int width;
  /** Colore del nodo, 4 byte di cui tre usati per rosso, verde, blu.*/
  protected int color;
  /** Errore sul colore del nodo,
   * 4 byte di cui tre usati per rosso, verde, blu,
   * se foglia vale sempre (0,0,0), altrimenti per ogni canale di
   * colore vale la max differenza con uno dei pixel rappresentati.
   */
  protected int error;
  /** Array dei figli del nodo, null se foglia. */
  protected QuadNode son[];
  /** Marcatura che vale true se il nodo e' stato preso nel presente
   * troncamento del quad-tree.
   */
  protected boolean taken;

  /** Contatore usato a fini statistici. */
  protected static int counter = 0;
  
  /** Colore dei bordi nell'immagine della suddivisione.
   * @see QuadTree#getSubdivisionImage
   * @see QuadNode#setDefaultColors */
  public static Color borderColor = Color.black;
  /** Colore dell'interno dei nodi che non sono singoli pixel, 
   * nell'immagine della suddivisione.
   * @see QuadTree#getSubdivisionImage
   * @see QuadNode#setDefaultColors */
  public static Color insideColor = Color.yellow;
  /** Colore dei nodi che sono singoli pixel,
   * nell'immagine della suddivisione.
   * @see QuadTree#getSubdivisionImage
   * @see QuadNode#setDefaultColors */
  public static Color pixelColor = Color.green; 

  /** Assegna i colori di default da usare nel generare l'immagine
   * della suddivisione:
   * colore dei bordi = nero,
   * colore dell'interno dei nodi che non sono singoli pixel = giallo,
   * colore dei nodi che sono singoli pixel = verde.
   */
  public static void setDefaultColors()
  {
    borderColor = Color.black; 
    insideColor = Color.yellow; 
    pixelColor = Color.green;
  }
  
  /** In base alla posizione come punti cardinali di un figlio del
   * nodo, ritorna l'indice di tale figlio nell'array dei figli.
   * @param   positionOfSon posizione del figlio come punti cardinali,
   *          deve valere uno tra NW, NE, SW, SE.
   * @return  la posizione del figlio nell'array dei figli, sara'
   *          un numero in [0..3]
   */
  public static int sonIndex(int positionOfSon)
  {
    switch(positionOfSon)
    {
      case NE: return 0;
      case SE: return 1;
      case NW: return 2;
      case SW: return 3;
    }
    return -1; // non deve succedere
  }

  /** In base all'indice di un figlio del nodo nell'array dei figli,
   * ritorna la posizione di tale figlio come punti cardinali.
   * @param   index  la posizione del figlio nell'array dei figli, 
   *                 deve essere un numero in [0..3]
   * @return  posizione del figlio come punti cardinali, sara' uno
   *          tra NW, NE, SW, SE.      
   */
  public static int indexPosition(int index)
  {
    switch(index)
    {
      case 0: return NE;
      case 1: return SE;
      case 2: return NW;
      case 3: return SW;
    }
    return -1; // non deve succedere
  }

  /**
  * Data l'ascissa del punto di aggancio di questo nodo,
  * ritorna l'ascissa del punto di aggancio
  * del nodo figlio corrispondente al punto cardinale specificato.
  * @param x0  ascissa del punto d'aggancio del nodo
  * @param positionOfSon  posizione del nodo figlio, come
  *            punti cardinali, deve valere uno tra NW, NE, SW, SE.
  * @return    ascissa del del punto d'aggancio del nodo figlio
  */
  public int anchorX(int x0, int positionOfSon)
  {
    if ((positionOfSon & WEST)!=0x0) return x0 + width/2;
    else return x0;
  }

  /**
  * Data l'ordinata del punto di aggancio di questo nodo,
  * ritorna l'ordinata del punto di aggancio
  * del nodo figlio corrispondente al punto cardinale specificato.
  * @param y0  ordinata del punto d'aggancio del nodo
  * @param positionOfSon  posizione del nodo figlio, come
  *            punti cardinali, deve valere uno tra NW, NE, SW, SE.
  * @return    ordinata del del punto d'aggancio del nodo figlio
  */
  public int anchorY(int y0, int positionOfSon)
  {
    if ((positionOfSon & SOUTH)!=0x0) return y0 + width/2;
    else return y0;
  }

  /** Costruisce nodo di colore nero e larghezza data.
   * @param w   la larghezza del nodo da creare
   */
  protected QuadNode(int w)
  {  son = null; color = 0; error = -1; taken = true; width = w;  }

  /** Costruisce nodo di colore e larghezza dati.
   * @param c   il colore da dare al nodo
   * @param w   la larghezza del nodo da creare
   */
  protected QuadNode(Color c, int w)
  {
    son = null; error = -1; taken = true; width = w;
    setColor(c);
  }

  /** Restituisce il colore del nodo. */
  public Color getColor()
  {  return QuadColor.makeColor(color);  }

  /** Assegna il colore del nodo. */
  public void setColor(Color c)
  {  color = QuadColor.makeColor(c);  }  

  /** Ritorna true se il nodo e' foglia, false altrimenti. */
  public boolean isLeaf()
  {  return (son==null);  }  
  
  /** Ritorna la larghezza del nodo. */
  public int getWidth()
  {  return width;  }
  
  /** Crea i figli di questo nodo. */
  protected void makeSons()
  {
    int i;
    taken = false;
    son = new QuadNode[4];
    for (i=0; i<4; i++)
    {
      son[i] = new QuadNode(width/2);
    }
  }

  /** 
   * Prepara un'immagine nel sotto-quadtree con radice in questo nodo,
   * marcando come "presi" tutti i nodi che hanno ampiezza data,
   * oppure sono piu' ampi ma foglie.
   * @param wid  larghezza dei nodi da prendere
   */
  protected void prepareSubImage(int wid)
  {
    // guarda se il nodo va preso 
    if (son==null) // foglia
    { taken = true; }
    else
    { taken = (width==wid); }
    // se non va preso guarda i figli
    if (!taken) 
    {
      son[sonIndex(NE)].prepareSubImage(wid);
      son[sonIndex(NW)].prepareSubImage(wid);
      son[sonIndex(SE)].prepareSubImage(wid); 
      son[sonIndex(SW)].prepareSubImage(wid); 
    }
  }

  /** 
   * Prepara un'immagine nel quadtree marcando come "presi" tutti i nodi 
   * che hanno ampiezza data o sono piu' larghi ma foglie.
   * Da chiamare prima di getImage().
   * @param wid  larghezza dei nodi da prendere
   */
  protected void prepareImage(int wid)
  {  this.prepareSubImage(wid);  }
  
  /** 
   * Prepara un'immagine nel sotto-quadtree con radice in questo nodo,
   * marcando come "presi" tutti i nodi che vi contribuiranno.
   * @param x0  coordinata x del punto di aggancio di questo nodo
   * @param y0  coordinata y del punto di aggancio di questo nodo
   * @param error1 soglia di errore che si applica fuori dal poligono
   * @param pol poligono
   * @param error2 soglia di errore che si applica dentro il poligono
   * @param error3 la minore tra le due soglie (calcolato)
   * Se il poligono e' null, gli ultimi tre parametri vengono ignorati.
   */
  protected void prepareSubImage(int x0, int y0, Color error1,
                       Polygon pol, Color error2, Color error3)
  {
    // guarda se il nodo va preso 
    if (son==null) // foglia
    { taken = true; }
    else
    {
      Color err = QuadColor.makeColor(error); // errore di questo nodo
      if (pol==null) // no poligono, conta solo soglia errore
      { taken = errorInBound(error1,err); }
      else if ( !pol.intersects((double)x0, (double)y0,
                (double)width, (double)width) ) // poligono disgiunto
      { taken = errorInBound(error1,err); }
      else if ( pol.contains((double)x0, (double)y0,
                (double)width, (double)width) ) // poligono contiene
      { taken = errorInBound(error2,err); }
      else // poligono interseca propriamente
      { taken = errorInBound(error3,err); }
    }
    // se non va preso guarda i figli
    if (!taken) 
    {
      int x1, y1;
      x1 = anchorX(x0, NE);
      y1 = anchorY(y0, NE);
      son[sonIndex(NE)].prepareSubImage(x1, y1, error1, pol, error2, error3);
      x1 = anchorX(x0, NW);
      y1 = anchorY(y0, NW);
      son[sonIndex(NW)].prepareSubImage(x1, y1, error1, pol, error2, error3); 
      x1 = anchorX(x0, SE);
      y1 = anchorY(y0, SE);
      son[sonIndex(SE)].prepareSubImage(x1, y1, error1, pol, error2, error3); 
      x1 = anchorX(x0, SW);
      y1 = anchorY(y0, SW);
      son[sonIndex(SW)].prepareSubImage(x1, y1, error1, pol, error2, error3); 
    }
  }

  /** Controlla se l'errore sul colore del nodo e' sotto la
   * soglia di tolleranza data.
   * @param  bound   la soglia di tolleranza 
   * @param  c       il colore da controllare rispetto alla tolleranza
   */
  public boolean errorInBound(Color bound, Color c)
  {
    if (bound==null) return true;
    if (c.getRed() > bound.getRed()) return false;
    if (c.getGreen() > bound.getGreen()) return false;
    if (c.getBlue() > bound.getBlue()) return false;
    return true;
  }
  
  /** 
   * Costruisce l'immagine nel sotto-quadtree con radice in questo nodo,
   * ottenuta prendendo i nodi marcati come "presi", e assegna i colori
   * dei pixel corrispondenti nella matrice di pixel data.
   * @param m   matrice di pixel in cui salvare i colori
   * @param x0  coordinata x del punto di aggancio di questo nodo
   * @param y0  coordinata y del punto di aggancio di questo nodo
   * @see QuadNode#getErrorSubImage
   * @see QuadNode#getSubdivisionSubImage
   */
  protected void getSubImage(PixelMatrix m, int x0, int y0)
  {
    if (taken) // foglia o nodo interno accettabile
    {
      counter++;
      for (int i=0; i<width; i++)
      for (int j=0; j<width; j++)
      { m.setColor(x0+i,y0+j,color); }
    }
    else // nodo interno con errore non accettabile
    {
      int x1, y1;
      x1 = anchorX(x0, NE);
      y1 = anchorY(y0, NE);
      son[sonIndex(NE)].getSubImage(m, x1, y1);
      x1 = anchorX(x0, NW);
      y1 = anchorY(y0, NW);
      son[sonIndex(NW)].getSubImage(m, x1, y1);
      x1 = anchorX(x0, SE);
      y1 = anchorY(y0, SE);
      son[sonIndex(SE)].getSubImage(m, x1, y1);
      x1 = anchorX(x0, SW);
      y1 = anchorY(y0, SW);
      son[sonIndex(SW)].getSubImage(m, x1, y1);
    }
  }
  
  /** 
   * Costruisce l'immagine degli errori nel sotto-quadtree con radice
   * in questo nodo, corrispondente all'immagine ottenuta
   * prendendo i nodi marcati come "presi", e assegna i colori dei
   * pixel corrispondenti nella matrice di pixel data.
   * Siccome gli errori tendono ad essere bassi, e l'immagine verrebbe
   * quasi nera, allora i valori sono mappati secondo la radice
   * quadrata.
   * @param m   matrice di pixel in cui salvare i colori
   * @param x0  coordinata x del punto di aggancio di questo nodo
   * @param y0  coordinata y del punto di aggancio di questo nodo
   * @see QuadNode#getSubImage
   * @see QuadNode#getSubdivisionSubImage
   */
  protected void getErrorSubImage(PixelMatrix m, int x0, int y0)
  {
    if (taken) // foglia o nodo interno accettabile
    {
      counter++;
      int newRed = (int)Math.sqrt((float)QuadColor.getRed(error))*16;
      int newGreen = (int)Math.sqrt(QuadColor.getGreen(error))*16;
      int newBlue = (int)Math.sqrt(QuadColor.getBlue(error))*16;
      int newError = QuadColor.makeColor(newRed, newGreen, newBlue);
      for (int i=0; i<width; i++)
      for (int j=0; j<width; j++)
      { m.setColor(x0+i,y0+j,newError); }
    }
    else // nodo interno con errore non accettabile
    {
      int x1, y1;
      x1 = anchorX(x0, NE);
      y1 = anchorY(y0, NE);
      son[sonIndex(NE)].getErrorSubImage(m, x1, y1);
      x1 = anchorX(x0, NW);
      y1 = anchorY(y0, NW);
      son[sonIndex(NW)].getErrorSubImage(m, x1, y1);
      x1 = anchorX(x0, SE);
      y1 = anchorY(y0, SE);
      son[sonIndex(SE)].getErrorSubImage(m, x1, y1);
      x1 = anchorX(x0, SW);
      y1 = anchorY(y0, SW);
      son[sonIndex(SW)].getErrorSubImage(m, x1, y1);
    }
  }

  /** 
   * Costruisce l'immagine della suddivisione nel sotto-quadtree con 
   * radice in questo nodo, corrispondente all'immagine ottenuta
   * prendendo i nodi marcati come "presi", e assegna i colori dei
   * pixel corrispondenti nella matrice di pixel data.
   * @param m   matrice di pixel in cui salvare i colori
   * @param x0  coordinata x del punto di aggancio di questo nodo
   * @param y0  coordinata y del punto di aggancio di questo nodo
   * @see QuadNode#getSubImage
   * @see QuadNode#getErrorSubImage
   */
  protected void getSubdivisionSubImage(PixelMatrix m, int x0, int y0)
  {
    if (taken) // foglia o nodo interno accettabile
    {
       counter++;
       if (width==1)  m.setColor(x0,y0,pixelColor);
       else
       {
         for (int i=0; i<width; i++)
         {
           m.setColor(x0+i,y0,borderColor); 
           m.setColor(x0+i,y0+width-1,borderColor);
         }
         for (int j=0; j<width; j++)
         {
           m.setColor(x0,y0+j,borderColor); 
           m.setColor(x0+width-1,y0+j,borderColor);
         }
         for (int i=1; i<width-1; i++)
         for (int j=1; j<width-1; j++)
           m.setColor(x0+i,y0+j,insideColor);
       }
    }
    else // nodo interno con errore non accettabile
    {
      int x1, y1;
      x1 = anchorX(x0, NE);
      y1 = anchorY(y0, NE);
      son[sonIndex(NE)].getSubdivisionSubImage(m, x1, y1);
      x1 = anchorX(x0, NW);
      y1 = anchorY(y0, NW);
      son[sonIndex(NW)].getSubdivisionSubImage(m, x1, y1);
      x1 = anchorX(x0, SE);
      y1 = anchorY(y0, SE);
      son[sonIndex(SE)].getSubdivisionSubImage(m, x1, y1);
      x1 = anchorX(x0, SW);
      y1 = anchorY(y0, SW);
      son[sonIndex(SW)].getSubdivisionSubImage(m, x1, y1);
    }
  }

  /**
  * Attribuisce anche ai nodi interni un colore, ricavato come media 
  * dei colori dei nodi figli.
  * Attribuisce anche un errore uguale alla max differenza, componente
  * per componente, del colore del nodo rispetto ai colori dei figli. 
  */
  protected void setError()
  {
    if (son==null) error = 0;
    else
    {
      int red, green, blue;
      red = green = blue = 0;
      for (int i=0; i<4; i++) son[i].setError();
      for (int i=0; i<4; i++)
      {
        red += QuadColor.getRed(son[i].color);
        green += QuadColor.getGreen(son[i].color); 
        blue += QuadColor.getBlue(son[i].color);
      }
      color = QuadColor.makeColor(red/4, green/4, blue/4);
      red = green = blue = 0;
      int d = 0;
      for (int i=0; i<4; i++)
      {
        if (son[i].isLeaf())
        {
          d = Math.abs(QuadColor.getRed(color)-QuadColor.getRed(son[i].color));
          if (d>red) red = d;
          d = Math.abs(QuadColor.getGreen(color)-QuadColor.getGreen(son[i].color));
          if (d>red) green = d;
          d = Math.abs(QuadColor.getBlue(color)-QuadColor.getBlue(son[i].color));
          if (d>red) blue = d;
        }
        else
        {
          d = QuadColor.getRed(son[i].error);
          if (d>red) red = d;
          d = QuadColor.getGreen(son[i].error);
          if (d>red) green = d;
          d = QuadColor.getBlue(son[i].error);
          if (d>red) blue = d;
        }
      }
      error = QuadColor.makeColor(red, green, blue);
    }  
  }
}
